#ifndef _PY_TERM_OBJ_H
#define _PY_TERM_OBJ_H

#define PY_SSIZE_T_CLEAN

#include <Python.h>
#include <structmember.h>

#include "term.h"
#include "py_namespace.h"


typedef struct {
    PyObject_HEAD
    LSUP_Term * ob_struct;
} TermObject;


static int
Term_init (TermObject *self, PyObject *args, PyObject *kwargs)
{
    unsigned char term_type;
    char *data = NULL, *datatype = NULL, *lang = NULL;

    static char *kwlist[] = {"", "", "datatype", "lang", NULL};

    if (!PyArg_ParseTupleAndKeywords (
            args, kwargs, "bs|zz", kwlist,
            &term_type, &data, &datatype, &lang))
        return -1;

    char *metadata = datatype ? datatype : lang;

    self->ob_struct = LSUP_term_new (
            (LSUP_TermType) term_type, data, metadata);
    if (!self->ob_struct) {
        PyErr_SetString (PyExc_ValueError, "Could not create term.");
        return -1;
    }

    return 0;
}


static int
Term_iriref_init (TermObject *self, PyObject *args, PyObject *kwargs)
{
    char *data = NULL;
    NSMapObject *nsm = NULL;

    static char *kwlist[] = {"data", "nsm", NULL};

    if (!PyArg_ParseTupleAndKeywords (
            args, kwargs, "|sO", kwlist, &data, &nsm))
        return -1;

    if (nsm && !PyObject_TypeCheck (nsm, &NSMapType)) {
        PyErr_SetString (PyExc_TypeError, "nsm is not a NSMap type.");
        return -1;
    }

    self->ob_struct = LSUP_iriref_new (
            data, (nsm ? nsm->ob_struct : NULL));
    if (!self->ob_struct) {
        PyErr_SetString (PyExc_ValueError, "Could not create term.");
        return -1;
    }

    return 0;
}


static int
Term_literal_init (TermObject *self, PyObject *args, PyObject *kwargs)
{
    char *data = NULL, *datatype = NULL, *lang = NULL;

    static char *kwlist[] = {"", "datatype", "lang", NULL};

    if (!PyArg_ParseTupleAndKeywords (
            args, kwargs, "s|zz", kwlist, &data, &datatype, &lang))
        return -1;

    if (lang)
        self->ob_struct = LSUP_lt_literal_new (data, lang);
    else {
        LSUP_Term *dtype = (datatype) ? LSUP_iriref_new (datatype, NULL) : NULL;
        self->ob_struct = LSUP_literal_new (data, dtype);
    }

    if (!self->ob_struct) {
        PyErr_SetString (PyExc_ValueError, "Could not create term.");
        return -1;
    }

    return 0;
}


static int
Term_bnode_init (TermObject *self, PyObject *args, PyObject *kwargs)
{
    char *data;
    static char *kwlist[] = {"data", NULL};

    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "|s", kwlist, &data))
        return -1;

    self->ob_struct = LSUP_term_new (LSUP_TERM_BNODE, data, NULL);
    if (!self->ob_struct) {
        PyErr_SetString (PyExc_ValueError, "Could not create term.");
        return -1;
    }

    return 0;
}


static void
Term_dealloc (TermObject *self)
{
    LSUP_term_free (self->ob_struct);
    Py_TYPE (self)->tp_free ((PyObject *) self);
}


static PyObject *
Term_get_type (TermObject *self, void *closure)
{
    return PyLong_FromLong (self->ob_struct->type);
}


static PyObject *
Term_get_data (TermObject *self, void *closure)
{ return PyUnicode_FromString (self->ob_struct->data); }


static PyObject *
Term_iriref_get_nsm (TermObject *self, void *closure)
{
    LSUP_Term *term = self->ob_struct;
    if (!LSUP_IS_IRI(term))
        Py_RETURN_NONE;

    LSUP_NSMap *nsm = LSUP_iriref_nsm (term);
    if (!nsm) Py_RETURN_NONE;

    NSMapObject *nsm_obj = PyObject_New (NSMapObject, &NSMapType);
    if (UNLIKELY (!nsm_obj)) return PyErr_NoMemory();

    nsm_obj->ob_struct = nsm;

    Py_INCREF (nsm_obj);
    return (PyObject *) nsm_obj;
}


static PyObject *
Term_iriref_get_prefix (TermObject *self, void *closure)
{
    LSUP_Term *term = self->ob_struct;
    if (! LSUP_IS_IRI (term))
        Py_RETURN_NONE;

    return PyUnicode_FromString (LSUP_iriref_prefix (term));
}


static PyObject *
Term_iriref_get_path (TermObject *self, void *closure)
{
    LSUP_Term *term = self->ob_struct;
    if (! LSUP_IS_IRI (term))
        Py_RETURN_NONE;

    return PyUnicode_FromString (LSUP_iriref_path (term));
}


static PyObject *
Term_iriref_get_frag (TermObject *self, void *closure)
{
    LSUP_Term *term = self->ob_struct;
    if (! LSUP_IS_IRI (term))
        Py_RETURN_NONE;

    return PyUnicode_FromString (LSUP_iriref_frag (term));
}


static PyObject *
Term_lit_get_datatype (TermObject *self, void *closure)
{
    if (!self->ob_struct->datatype) Py_RETURN_NONE;

    char *dtype_data =
        self->ob_struct->type == LSUP_TERM_LT_LITERAL ? DEFAULT_DTYPE :
        self->ob_struct->datatype->data;

    TermObject *datatype = (TermObject *) Py_TYPE (self)->tp_alloc (
            Py_TYPE (self), 0);
    if (!datatype) return PyErr_NoMemory();

    datatype->ob_struct = LSUP_iriref_new (dtype_data, NULL);

    Py_INCREF (datatype);
    return (PyObject *) datatype;
}


static PyObject *
Term_lit_get_lang (TermObject *self, void *closure)
{
    if (
            self->ob_struct->type != LSUP_TERM_LT_LITERAL
            || ! self->ob_struct->lang
            || strlen (self->ob_struct->lang) == 0)
        Py_RETURN_NONE;

    return PyUnicode_FromString (self->ob_struct->lang);
}


static PyGetSetDef Term_getsetters[] = {
    {"_type", (getter) Term_get_type, NULL, "Term type.", NULL},
    {"_data", (getter) Term_get_data, NULL, "Term data.", NULL},
    {
        "_nsm", (getter) Term_iriref_get_nsm,
        NULL, "IRI ref namespace map.", NULL
    },
    {
        "_prefix", (getter) Term_iriref_get_prefix,
        NULL, "IRI ref prefix.", NULL
    },
    {
        "_path", (getter) Term_iriref_get_path,
        NULL, "IRI ref path after prefix.", NULL
    },
    {
        "_frag", (getter) Term_iriref_get_frag,
        NULL, "IRI ref fragment.", NULL
    },
    {
        "_datatype", (getter) Term_lit_get_datatype,
        NULL, "Literal term data type.", NULL
    },
    {
        "_lang", (getter) Term_lit_get_lang,
        NULL, "Literal term language tag.", NULL
    },
    {NULL}
};


static PyGetSetDef IRIRef_getsetters[] = {
    {"data", (getter) Term_get_data, NULL, "IRI string.", NULL},
    {
        "nsm", (getter) Term_iriref_get_nsm,
        NULL, "Namespace map.", NULL
    },
    {
        "prefix", (getter) Term_iriref_get_prefix,
        NULL, "IRI ref prefix.", NULL
    },
    {
        "path", (getter) Term_iriref_get_path,
        NULL, "IRI ref path after prefix.", NULL
    },
    {
        "frag", (getter) Term_iriref_get_frag,
        NULL, "IRI ref fragment.", NULL
    },
    {NULL}
};


static PyGetSetDef Literal_getsetters[] = {
    {"data", (getter) Term_get_data, NULL, "Literal data.", NULL},
    {
        "datatype", (getter) Term_lit_get_datatype,
        NULL, "Data type.", NULL
    },
    {
        "lang", (getter) Term_lit_get_lang,
        NULL, "Language tag.", NULL
    },
    {NULL}
};


static PyGetSetDef BNode_getsetters[] = {
    {"data", (getter) Term_get_data, NULL, "Blank node label.", NULL},
    {NULL}
};


static PyObject *
Term_richcmp (PyObject *obj1, PyObject *obj2, int op);


static Py_hash_t
Term_hash (PyObject *self)
{ return LSUP_term_hash (((TermObject *)self)->ob_struct); }


PyTypeObject TermType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "term.Term",
    .tp_doc = "RDF term",
    .tp_basicsize = sizeof (TermObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
    .tp_init = (initproc) Term_init,
    .tp_dealloc = (destructor) Term_dealloc,
    .tp_getset = Term_getsetters,
    .tp_richcompare = Term_richcmp,
    .tp_hash = Term_hash,
};


PyTypeObject IRIRefType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "term.IRIRef",
    .tp_doc = "RDF IRI reference.",
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_base = &TermType,
    .tp_init = (initproc) Term_iriref_init,
    .tp_getset = IRIRef_getsetters,
};


PyTypeObject LiteralType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "term.Literal",
    .tp_doc = "RDF Literal.",
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_base = &TermType,
    .tp_init = (initproc) Term_literal_init,
    .tp_getset = Literal_getsetters,
};


PyTypeObject BNodeType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "term.BNode",
    .tp_doc = "RDF Blanbk Node.",
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_base = &TermType,
    .tp_init = (initproc) Term_bnode_init,
    .tp_getset = BNode_getsetters,
};


static PyObject *
Term_richcmp (PyObject *obj1, PyObject *obj2, int op)
{
    PyObject *result = NULL;

    if (
        ! PyObject_TypeCheck (obj1, &TermType) ||
        ! PyObject_TypeCheck (obj2, &TermType)
    ) return NULL;

    int c = 0;
    LSUP_Term *t1 = ((TermObject *) obj1)->ob_struct;
    LSUP_Term *t2 = ((TermObject *) obj2)->ob_struct;

    switch (op) {
        case Py_LT: result = Py_NotImplemented; break;
        case Py_LE: result = Py_NotImplemented; break;
        case Py_EQ: c = LSUP_term_equals (t1, t2); break;
        case Py_NE: c = ! LSUP_term_equals (t1, t2); break;
        case Py_GT: result = Py_NotImplemented; break;
        case Py_GE: result = Py_NotImplemented; break;
    }

    if (!result) result = c ? Py_True : Py_False;

    Py_INCREF(result);
    return result;
 }

// END _PY_TERM_OBJ_H
#endif
